package top.purity.server.gateway.filter;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import top.purity.framework.common.GlobalConstant;
import top.purity.framework.common.result.GlobalRespCode;
import top.purity.framework.common.result.RespResult;
import top.purity.server.gateway.feign.input.TenantFeign;
import top.purity.server.gateway.feign.input.UserFeign;
import top.purity.server.gateway.feign.input.dto.PermissionDTO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.concurrent.*;

/**
 * @version 1.0
 * @Author Lmh
 * @Description 网关拦截
 * @CreateTime 2023-09-10 10:19
 */

@Configuration
@Slf4j
@RequiredArgsConstructor
public class SaTokenFilter {

    private final UserFeign userFeign;

    private final TenantFeign tenantFeign;

    @Bean
    public SaReactorFilter saReactorFilter() {

        return new SaReactorFilter()
            .addInclude("/**")
            .addExclude("/favicon.ico", "/sso/**", "/*/sso/**", "/user/account/getInfo", "/user/account/getPermission")
            .setAuth(auth -> {
                log.info("---------- 进入Sa-Token全局认证 -----------");

                // 禁止直接从网关调用rpc接口
                SaRouter.match("/*/rpc/**").back(JSONUtil.toJsonStr(RespResult.error(GlobalRespCode.UN_AUTH)));

                // 对 token 进行校验
                SaRequest request = SaHolder.getRequest();
                String header = request.getHeader(GlobalConstant.AUTH_HEADER);
                if (StrUtil.isBlank(header)) {
                    SaRouter.back(JSONUtil.toJsonStr(RespResult.error(GlobalRespCode.UN_LOGIN)));
                }

                Object loginId = StpUtil.getLoginIdByToken(header);
                if (ObjUtil.isNull(loginId)) {
                    SaRouter.back(JSONUtil.toJsonStr(RespResult.error(GlobalRespCode.LOGIN_EXPIRE)));
                }

                List<String> split = StrUtil.split((String) loginId, GlobalConstant.LOGIN_ID_SPLIT);
                Long userId = Long.valueOf(split.get(GlobalConstant.LOGIN_ID_INDEX_USER));
                Long tenantId = Long.valueOf(split.get(GlobalConstant.LOGIN_ID_INDEX_TENANT));

                // 异步回调，网关内部不允许使用同步请求调用， 如果是根管理不需要鉴权
                Boolean rootAdmin = isRootAdmin(userId, tenantId);
                if (rootAdmin) {
                    SaRouter.stop();
                }

                SaRouter.match("/**", "", () -> {
                    // 找到当前路径对应的权限码
                    String requestPath = request.getRequestPath();
                    // 异步回调，网关内部不允许使用同步请求调用，获取账号对应权限，与当前进行对比
                    PermissionDTO menuByPath = getMenuByPath(requestPath);
                    if (ObjUtil.isNull(menuByPath)) {
                        SaRouter.back(JSONUtil.toJsonStr(RespResult.error(GlobalRespCode.UN_AUTH)));
                    }

                    StpUtil.checkPermission(menuByPath.getMenuCode());
                }).stop();

            })
            .setError(error -> {
                log.error("---------- 进入Sa-Token异常处理 -----------", error);
                error.printStackTrace();
                String message = error.getMessage();
                return RespResult.error(message);
            })
            // 前置函数：在每次认证函数之前执行（BeforeAuth 不受 includeList 与 excludeList 的限制，所有请求都会进入）
            .setBeforeAuth(before -> {
                String requestPath = SaHolder.getRequest().getRequestPath();
                String url = SaHolder.getRequest().getUrl();
                log.info("---------- 进入Sa-Token请求前置处理 : path ==> {}, url ==> {} -----------", requestPath, url);

                SaResponse response = SaHolder.getResponse();
                response.setHeader("Access-Control-Allow-Origin", "*");
                response.setHeader("Access-Control-Allow-Methods", "*");
                response.setHeader("Access-Control-Allow-Headers", "*");
                response.setHeader("Access-Control-Max-Age", "3600");
                response.setHeader("Content-Type", "application/json;charset=utf-8");

                SaRouter.match(SaHttpMethod.OPTIONS).stop();
            });
    }

    /**
     * 判断是否为 超管，如果是超管则不需要进行权限认证
     * @param userId 用户id
     * @param tenantId 租户id
     * @return 是否为超管
     */
    private Boolean isRootAdmin(Long userId, Long tenantId) {
        try (ExecutorService getRootAdmin = Executors.newSingleThreadExecutor()) {
            Future<Boolean> rootAdmin = getRootAdmin.submit(() -> userFeign.isRootAdmin(userId, tenantId));
            return rootAdmin.get();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("判断是否为 超管出现错误", e);
        }

        return false;
    }

    /**
     * 获取当前路径对应的权限标识符
     * @param requestPath 路径
     * @return 标识符
     */
    private PermissionDTO getMenuByPath(String requestPath) {
        try (ExecutorService getMenuByPath = Executors.newSingleThreadExecutor()) {
            Future<PermissionDTO> menuByPath = getMenuByPath.submit(() -> tenantFeign.getMenuByPath(requestPath));
            return menuByPath.get();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("获取当前路径对应的权限标识符出现错误", e);
        }

        return null;
    }
}
